PHP 7 新特性

变量类型:

类型提示
PHP 版本函数的参数和返回值增加了类型限定. 为什么 PHP 要加入类型, 实际上此项特性是为了 PHP 7.1 版本的 JIT 特性做准备, 增加类型后 PHP JIT 可以准确判断类型, 生成最佳的机器码
function test(int $a, string $b, array $c) : int 
{
    // do something
}

PHP 5.0 首次提出函数参数(只针对对象类型)的类型提示(Type Hint),之后 PHP 5.1 进一步扩展到可针对数组类型。

<?php
class Person
{
    public $name;
    public $id;
    function __construct($name, $id) {
        $this->name = $name;
        $this->id = $id;
    }
}

$person = new Person("Tom", 101);

function printPerson(Person $person) {  // 对象类型参数提示
    echo "Name: ", $person->name, ", ID: ", $person->id, ".";
}

printPerson($person);

$skills = ["PHP 7", "C++", "Java", "Golang", "Python"];  // PHP 5.4 起可以使用数组定义短语法

function printSkills(array $skills) {   // 数组类型参数提示
    foreach ($skills as $skill) {
        echo $skill, "\n";
    }
}

printSkills($skills);

PHP 7 增加了对标量参数类型的支持:

  • int
  • float
  • string
  • bool
function printRecord(string $name, int $id, float $salary, bool $sex) {
    echo $sex ? "$name, $id, $salary, male." : "$name, $id, $salary, female.";
}

printRecord("Tom", 101, 5650.00, TRUE);     // Tom, 101, 5650, male.
printRecord("Suzy", 101, 5650.00, FALSE);   // Suzy, 101, 5650, female.

PHP 7 的函数/方法返回值类型提示:PHP 7还支持了函数/方法返回值类型提示

function getRecord(string $name, int $id, float $salary, bool $sex) : string { // 返回值定义类型为字符串
    return $sex ? "$name, $id, $salary, male." : "$name, $id, $salary, female.";
}

getRecord("Tom", 101, 5650.00, TRUE);

// return  "Tom, 101, 5650, male.";

return的返回类型提示,跟 PHPDoc 的 @return 注解是完全不同的两个方面,@return 只是“好言规劝”或向IDE“友好反馈”返回类型应该是什么,而对于实际的返回类型不具约束力。return的返回类型提示则具有对返回类型的运行时强制约束力

类型提示涉及父类继承或接口实现时
interface iFoo {}
class Foo implements iFoo {}
class Bar extends Foo {}

function coo(iFoo $foo) : iFoo {
    return $foo;
}

coo(new Foo());
coo(new Bar());

function gaa(Foo $foo) : iFoo {
    return $foo;
}

gaa(new Foo());
gaa(new Bar());

function zii(Bar $bar) : Foo {
    return $bar;
}

zii(new Foo());  // TypeError: Argument 1 passed to zii() must be an instance of Bar, instance of Foo given on line 1
zii(new Bar());
严格类型约束
function xii(array $a, string $s) : int {
    print_r($a);
    echo $s, "\n";
    return "101";
}

xii([1, 2, 3, 4, 5, 6, 7, 8], 101);
xii(101, 102);  // TypeError: Argument 1 passed to xii() must be of the type array, integer given on line 1

对于标量类型提示,我们的参数也罢、返回值也罢,其类型跟类型提示不一致也不影响程序运行(注:对象及数组类型具备约束力,注意区别)。这可能不是我们想要的。解决办法就是在 PHP 脚本文件的第一条语句的位置放上:declare(strict_types=1);。这是个文件级别的指令,同时不影响其他包含文件——主要是考虑向后兼容及不影响各类扩展、内建代码。

PHP7 新增的生成器特性:

生成器委托(Generator Delegation)
<?php
declare(strict_types=1);

$seh_seh_liām = function () {
    $generator = function () {
        yield from range(1, 3);

        foreach (range(4, 6) as $i) {
            yield $i;
        }
    };

    foreach ($generator() as $value) {
        echo "每天念 PHP 是最好的编程语言 6 遍...第 $value 遍...", PHP_EOL;
    }
};

$seh_seh_liām();
生成器返回表达式(Generator Return Expression)

生成器返回表达式(Generator Return Expression)为生成器函数提供了增强内力,在 PHP 7 之前是无法在生成器函数内返回值的。

<?php
$traverser = (function () {
  yield "foo";
  yield "bar";
  return "value";
})();

$traverser->getReturn();

foreach ($traverser as $value) {
    echo "{$value}", PHP_EOL;
}

$traverser->getReturn();  // "value"
生成器与Coroutine
<?php
declare(strict_types=1);

class Coroutine
{
    public static function create(callable $callback) : Generator
    {
        return (function () use ($callback) {
            try {
                yield $callback;
            } catch (Exception $e) {
                echo "OH.. an error, but don't care and continue...", PHP_EOL;
            }
       })();
    }

    public static function run(array $cos)
    {
        $cnt = count($cos);
        while ($cnt > 0) {
            $loc = random_int(0, $cnt-1);  // 用 random 模拟调度策略。
            $cos[$loc]->current()();
            array_splice($cos, $loc, 1);
            $cnt--;
        }
    }
}

$co = new Coroutine();

$cos = [];
for ($i = 1; $i <= 10; $i++) {
    $cos[] = $co::create(function () use ($i) { echo "Co.{$i}.", PHP_EOL; });
}
$co::run($cos);

$cos = [];
for ($i = 1; $i <= 20; $i++) {
    $cos[] = $co::create(function () use ($i) { echo "Co.{$i}.", PHP_EOL; });
}
$co::run($cos);

新的写法:

空合并操作符(Null Coalesce Operator)
$name = $name ?? "NoName";  // 如果$name有值就取其值,否则设$name成"NoName"
飞船操作符(Spaceship Operator)

形式:(expr) <=> (expr)

左边运算对象小,则返回-1;左、右两边运算对象相等,则返回0;左边运算对象大,则返回1。

$name = ["Simen", "Suzy", "Cook", "Stella"];
usort($name, function ($left, $right) {
    return $left <=> $right;
});
print_r($name);
常量数组(Constant Array)

PHP 7 之前只允许类/接口中使用常量数组,现在 PHP 7 也支持非类/接口的普通常量数组了。

define("USER", [
  "name"  => "Simen",
  "sex"   => "Male",
  "age"   => "38",
  "skill" => ["PHP", "MySQL", "C"]
]);
// USER["skill"][2] = "C/C++";  // PHP Fatal error:  Cannot use temporary expression in write context in...
统一了变量语法
$goo = [
    "bar" => [
        "baz" => 100,
        "cug" => 900
    ]
];

$foo = "goo";

$$foo["bar"]["baz"];  // 实际为:($$foo)['bar']['baz']; PHP 5 中为:${$foo['bar']['baz']};
                      // PHP 7 中一个笼统的判定规则是,由左向右结合。
Throwable 接口

这是 PHP 7 引进的一个值得期待的新特性,将极大增强 PHP 错误处理能力。PHP 5 的 try ... catch ... finally 无法处理传统错误,如果需要,你通常会考虑用 set_error_handler() 来 Hack 一下。但是仍有很多错误类型是 set_error_handler() 捕捉不到的。PHP 7引入 Throwable 接口,错误及异常都实现了 Throwable,无法直接实现 Throwable,但可以扩展 \Exception 和 \Error 类。可以用 Throwable 捕捉异常跟错误。\Exception 是所有PHP及用户异常的基类;\Error 是所有内部PHP错误的基类。

$name = "Tony";
try {
    $name = $name->method();
} catch (\Error $e) {
    echo "出错消息 --- ", $e->getMessage(), PHP_EOL;
}

try {
    $name = $name->method();
} catch (\Throwable $e) {
    echo "出错消息 --- ", $e->getMessage(), PHP_EOL;
}

try {
    intdiv(5, 0);
} catch (\DivisionByZeroError $e) {
    echo "出错消息 --- ", $e->getMessage(), PHP_EOL;
}
use 组合声明

use 组合声明可以减少 use 的输入冗余。

use PHPGoodTaste\Utils\{
    Util,
    Form,
    Form\Validation,
    Form\Binding
};
一次捕捉多种类型的异常 / 错误

PHP 7.1 新添加了捕获多种异常/错误类型的语法——通过竖杠“|”来实现。

try {
      throw new LengthException("LengthException");
    //   throw new DivisionByZeroError("DivisionByZeroError");
    //   throw new Exception("Exception");
} catch (\DivisionByZeroError | \LengthException $e) {
    echo "出错消息 --- ", $e->getMessage(), PHP_EOL;
} catch (\Exception $e) {
    echo "出错消息 --- ", $e->getMessage(), PHP_EOL;
} finally {
    // ...
}
可见性修饰符的变化

PHP 7.1 之前的类常量是不允许添加可见性修饰符的,此时类常量可见性相当于 public。PHP 7.1 为类常量添加了可见性修饰符支持特性。总的来说,可见性修饰符使用范围如下所示:

函数/方法:public、private、protected、abstract、final

  • 类:abstract、final
  • 属性/变量:public、private、protected
  • 类常量:public、private、protected
class YourClass 
{
    const THE_OLD_STYLE_CONST = "One";

    public const THE_PUBLIC_CONST = "Two";
    private const THE_PRIVATE_CONST = "Three";
    protected const THE_PROTECTED_CONST = "Four";
}
iterable 伪类型

PHP 7.1 引入了 iterable 伪类型。iterable 类型适用于数组、生成器以及实现了 Traversable 的对象,它是 PHP 中保留类名。

$fn = function (iterable $it) : iterable {
    $result = [];
    foreach ($it as $value) {
        $result[] = $value + 1000;
    }
    return $result;
};

$fn([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
可空类型(Nullable Type)

PHP 7.1 引入了可空类型。看看新兴的 Kotlin 编程语言的一个噱头特性就是可空类型。PHP 越来越像“强类型语言了”。对于同一类型的强制要求,可以设置其是否可空。

$fn = function (?int $in) {     //  ? 的形式表明函数参数或者返回值的类型要么为指定类型
    return $in ?? "NULL";
};

$fn(null);
$fn(5);
$fn();  // TypeError: Too few arguments to function {closure}(), 0 passed in ...

此方法也可用于接口函数的定义:

interface Fooable {
    function foo(?Fooable $f);
}

但有一个需要注意的地方:如果函数本身定义了参数类型并且没有默认值,即使是可空的,也不能省略,否则会触发错误

function foo_nullable(?Bar $bar) {}

foo_nullable(new Bar); // 可行
foo_nullable(null); // 可行
foo_nullable(); // 不可行
Void 返回类型

PHP 7.1 引入了 Void 返回类型。

function first(): void {
    // ...
}

function second(): void {
    // ...
    return;
}
list 的方括号简写

我们知道在 PHP5.4 之前只能通过 array() 来定义数组,5.4之后添加了 [] 的简化写法

// 5.4 之前
$array = array(1, 2, 3);
$array = array("a" => 1, "b" => 2, "c" => 3);

// 5.4 及之后
$array = [1, 2, 3];
$array = ["a" => 1, "b" => 2, "c" => 3];

引申到另外一个问题上,如果我们要把数组的值赋值给不同的变量,可以通过 list 来实现:

$array = ['a', 'b', 'c'];

list($a, $b, $c) = $array;  // list() 仅能用于数字索引的数组,并假定数字索引从 0 开始。

最新的变化:

// 通过 [] 的简写
[$a, $b, $c] = $array;

// list 指定 key
["a" => $a, "b" => $b, "c" => $c] = $array;

PHP7.1 实现了这个特性。但是要注意的是:出现在左值中的 [] 并不是数组的简写,是 list() 的简写。
但是并不仅仅如此,新的 list() 的实现并不仅仅可以出现在左值中,也能在 foreach 循环中使用:

foreach ($points as ["x" => $x, "y" => $y]) {
    var_dump($x, $y);
}

list() 和 [] 不能相互嵌套使用

// 不合法
list([$a, $b], [$c, $d]) = [[1, 2], [3, 4]];

// 不合法
[list($a, $b), list($c, $d)] = [[1, 2], [3, 4]];

// 合法
[[$a, $b], [$c, $d]] = [[1, 2], [3, 4]];
允许在 list 中指定 key
$array = ["a" => 1, "b" => 2, "c" => 3];
["a" => $a, "b" => $b, "c" => $c] = $array;

以上代码相当于:

$a = $array['a'];
$b = $array['b'];
$c = $array['c'];

和以往的区别在于以往的 list() 的实现相当于 key 只能是 0, 1, 2, 3 的数字形式并且不能调整顺序。执行以下语句:

list($a, $b) = [1 => '1', 2 => '2'];

会得到 PHP error: Undefined offset: 0... 的错误。

而新的实现则可以通过以下方式来调整赋值:

list(1 => $a, 2 => $b) = [1 => '1', 2 => '2'];

不同于数组的是,list 并不支持混合形式的 key,以下写法会触发解析错误

// Parse error: syntax error, ...
list($unkeyed, "key" => $keyed) = $array;

更复杂的情况,list 也支持复合形式的解析:

$points = [
    ["x" => 1, "y" => 2],
    ["x" => 2, "y" => 1]
];

list(list("x" => $x1, "y" => $y1), list("x" => $x2, "y" => $y2)) = $points;

$points = [
    "first" => [1, 2],
    "second" => [2, 1]
];

list("first" => list($x1, $y1), "second" => list($x2, $y2)) = $points;

以及循环中使用:

$points = [
    ["x" => 1, "y" => 2],
    ["x" => 2, "y" => 1]
];

foreach ($points as list("x" => $x, "y" => $y)) {
    echo "Point at ($x, $y)", PHP_EOL;
}

多条件 catch

在以往的 try ... catch 语句中,每个 catch 只能设定一个条件判断:

try {
    // Some code...
} catch (ExceptionType1 $e) {
    // 处理 ExceptionType1
} catch (ExceptionType2 $e) {
    // 处理 ExceptionType2
} catch (\Exception $e) {
    // ...
}

新的实现中可以在一个 catch 中设置多个条件,相当于或的形式判断:

try {
    // Some code...
} catch (ExceptionType1 | ExceptionType2 $e) {
    // 对于 ExceptionType1 和 ExceptionType2 的处理
} catch (\Exception $e) {
    // ...
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容

  • 1. ?? 运算符(NULL 合并运算符) 把这个放在第一个说是因为我觉得它很有用。用法: $a = $_GET[...
    司马东阳阅读 267评论 0 0
  • PHP7 已经出来1年了,PHP7.1也即将和大家见面,这么多好的特性,好的方法,为什么不使用呢,也希望PHP越来...
    梦幻_78af阅读 2,029评论 1 10
  • PHP 学习目录 ├─PHP视频教程 1 LAMP网站构建 │ ├─PHP教程 1.1.1 新版视频形式介绍│ ...
    曹渊说创业阅读 16,066评论 29 417
  • 我把这次旅行当作我的生日之旅!过个不一样一点的生日,远离城市的喧嚣。我们一行人,有老司机猴哥、机智如我、奶哥、斌斌...
    方方方好厉害阅读 368评论 0 4
  • 八月十五,好不容易抢到回家的高铁票却因突如其然的超强台风退票,十四晚还在担心回不了家,见新闻报道台风转移阵地,果断...
    koushileaf阅读 194评论 0 0